// Extend.js (http://code.google.com/p/extendjs/)
// Copyright 2010 Shumpei Shiraishi All rights reserved.
// Licensed under the MIT license.

(function(_global) {
    // Array
    var NATIVE_METHODS_ARRAY = [
	"toString", "toLocaleString", "concat", "join", "pop", "push", "reverse", "shift",
	"slice", "sort", "splice", "unshift", "indexOf",
	"lastIndexOf", "every", "some", "forEach", "map",
	"filter", "reduce", "reduceRight"
    ];
    X.extend("Array", function(value) {
	return value instanceof Array;
    });
    X.Array.extend({
	isArray: function() {
	    return true;
	},
	indexOf: function(obj, opt_fromIndex) {
	    var arr = this.__val;
	    var fromIndex = opt_fromIndex == null?
		0 : (opt_fromIndex < 0 ?
		     Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
	    for (var i = fromIndex; i < arr.length; i++) {
		if (i in arr && arr[i] === obj)
		    return i;
	    }
	    return -1;
	},
	lastIndexOf: function(obj, opt_fromIndex) {
	    var arr = this.__val;
	    var fromIndex = opt_fromIndex == null?
		0 : (opt_fromIndex < 0 ?
		     Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
	    for (var i = fromIndex; i >= 0; i--) {
		if (i in arr && arr[i] === obj)
		    return i;
	    }
	    return -1;
	},
	pluck: function(property) {
	    var results = [];
	    this.each(function(value) {
		results.push(value[property]);
	    });
	    return results;
	},
	contains: function(obj) {
	    return this.indexOf(obj) >= 0;
	},
	isEmpty: function() {
	    return this.__val.length === 0;
	},
	clear: function() {
	    this.__val.length = 0;
	},
	addAll: function(array) {
	    for (var i = 0, n = array.length; i < n; i++) {
		this.__val.push(array[i]);
	    }
	},
	insert: function(obj) {
	    if (!this.contains(obj)) {
		this.__val.push(obj);
	    }
	},
	insertAt: function(obj, opt_index) {
	    this.splice(opt_index, 0, obj);
	},
	insertBefore: function(obj, opt_obj2) {
	    var i;
	    if (arguments.length == 2 || (i = this.indexOf(opt_obj2)) < 0) {
		this.__val.push(obj);
	    } else {
		this.insertAt(obj, i);
	    }
	},
	splice: function(index, howMany, var_args) {
	    return Array.prototype.splice.apply(
		this.__val, slice(arguments, 1));
	},
	flatten: function() {
	    var result = [];
	    var arr = this.__val;
	    for (var i = 0, n = arr.length; i < n; i++) {
		if (arr[i] instanceof Array) {
		    result.push.apply(result, X(arr[i]).flatten());
		} else {
		    result.push(arr[i]);
		}
	    }
	    return result;
	},
	rotate: function(n) {
	    var array = this.__val;
	    if (array.length) {
		n %= array.length;
		if (n > 0) {
		    Array.prototype.unshift.apply(array, array.splice(-n, n));
		} else if (n < 0) {
		    Array.prototype.push.apply(array, array.splice(0, -n));
		}
	    }
	    return array;
	},
	remove: function(obj) {
	    var i = this.indexOf(obj);
	    var rv;
	    if ((rv = i >= 0)) {
		this.removeAt(i);
	    }
	    return rv;
	},
	removeAt: function(i) {
	    // use generic form of splice
	    // splice returns the removed items and if successful the length of that
	    // will be 1
	    return Array.prototype.splice.call(this.__val, i, 1).length == 1;
	},
	each: function(fn, ctx) {
	    this.forEach(fn, ctx);
	},
	forEach: function(fn, ctx) {
	    var array = this.__val;
	    var l = array.length;
	    for (var i = 0; i < l; i++) {
		if (i in array) {
		    fn.call(ctx, val[i], i, array);
		}
	    }
	},
	forEachRight: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    for (var i = l - 1; i >= 0; --i) {
		if (i in arr) {
		    f.call(opt_obj, arr[i], i, arr);
		}
	    }
	},
	filter: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    var res = [];
	    var resLength = 0;
	    for (var i = 0; i < l; i++) {
		if (i in arr) {
		    var val = arr[i];  // in case f mutates arr2
		    if (f.call(opt_obj, val, i, arr)) {
			res[resLength++] = val;
		    }
		}
	    }
	    return res;
	},
	map: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    var res = new Array(l);
	    for (var i = 0; i < l; i++) {
		if (i in arr) {
		    res[i] = f.call(opt_obj, arr[i], i, arr);
		}
	    }
	    return res;
	},
	reduce: function(f, val, opt_obj) {
	    var arr = this.__val;
	    var rval = val;
	    this.forEach(function(val, index) {
		rval = f.call(opt_obj, rval, val, index, arr);
	    });
	    return rval;
	},
	reduceRight: function(f, val, opt_obj) {
	    var arr = this.__val;
	    var rval = val;
	    X(arr).forEachRight(arr, function(val, index) {
		rval = f.call(opt_obj, rval, val, index, arr);
	    });
	    return rval;
	},
	some: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    for (var i = 0; i < l; i++) {
		if (i in arr && f.call(opt_obj, arr[i], i, arr)) {
		    return true;
		}
	    }
	    return false;
	},
	every: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    for (var i = 0; i < l; i++) {
		if (i in arr && !f.call(opt_obj, arr[i], i, arr)) {
		    return false;
		}
	    }
	    return true;
	},
	find: function(f, opt_obj) {
	    var i = this.findIndex(f, opt_obj);
	    return i < 0 ? null : this.__val[i];
	},
	findIndex: function(arr, f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    for (var i = 0; i < l; i++) {
		if (i in arr && f.call(opt_obj, arr[i], i, arr)) {
		    return i;
		}
	    }
	    return -1;
	},
	findRight: function(f, opt_obj) {
	    var i = this.findIndexRight(f, opt_obj);
	    return i < 0 ? null : this.__val[i];
	},
	findIndexRight: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;  // must be fixed during loop... see docs
	    for (var i = l - 1; i >= 0; i--) {
		if (i in arr && f.call(opt_obj, arr[i], i, arr)) {
		    return i;
		}
	    }
	    return -1;
	},
	removeIf: function(f, opt_obj) {
	    var arr = this.__val;
	    var i = this.findIndex(f, opt_obj);
	    if (i >= 0) {
		this.removeAt(arr, i);
		return true;
	    }
	    return false;
	},
	length: function(len) {
	    if (typeof len === "number") {
		this.__val.length = len;
	    }
	    return this.__val.length;
	}
    });
    X.Array.mixin(Array.prototype, NATIVE_METHODS_ARRAY);
})(this);
